/**
 * 定义Board构造函数，用于处理数据 Create By YangHuiJun
 */
const Board = function () {
    //定义数组存放棋子数据
    this.data = [];
    //游戏分数
    this.score = 0;
    //记录游戏胜利或失败
    this.WIN_OR_FAIL = 0;
}

/**
 * 初始化二维数组
 */
Board.prototype.datainit = function () {
    for (var arr = [], x = 0; x < 4; x++) {
        arr[x] = [];
        for (var y = 0; y < 4; y++) {
            arr[x][y] = 0;
        }
    }
    this.data = arr;
}

/**  找到所有的空位置，在其中一个位置生成棋子
 * 
 * @param  view 为View对象，用于调用该对象的addNum方法创建棋子
 */
Board.prototype.findZero = function (view) {
    //定义空数组，用于存储空位置的坐标数据
    let empty = [];
    //遍历数组，找出所有的空位置，并存储于empty中
    for (let x = 0; x < 4; x++) {
        for (let y = 0; y < 4; y++) {
            if (this.data[x][y] === 0) {
                empty.push({ x: x, y: y });
            }
        }
    }
    //随机抽取一个空位置
    let randomEmpty = empty[Math.floor(Math.random() * empty.length)];
    //给该位置随机选取2或4赋初始值
    this.data[randomEmpty.x][randomEmpty.y] = Math.random() < 0.5 ? 2 : 4;
    //调用view对象的方法创建棋子
    view.addNum(randomEmpty.x, randomEmpty.y, this.data[randomEmpty.x][randomEmpty.y]);
}

/**  把0移到数组后面Create By YangHuiJun
 * 
 * @param  arr 需要进行移0的数组 
 */
Board.prototype.moveNumZero = function (arr) {
    //设置正则表达式，提取出非0的数
    let reg = /([12]0\d\d)|([1-9]\d\d)|([1-9]\d)|[248]/g;
    arr = arr.join(",").match(reg);
    //当原数组所有的值为0时，正则匹配返回null,则需对数组重新赋值
    if (arr == null) {
        arr = [0, 0, 0, 0];
    }
    //获取返回后的数组的长度，实际是返回非0数据的个数
    let length = arr.length;
    //当返回非0数据的个数不为4时，即原数组的值有0存在，则需对当前数组末尾补0，实现把原数组的0移到数组的后面
    if (length != 4) {
        for (let i = length; i < 4; i++) {
            arr[i] = 0;
        }
    }
    //由于正则匹配返回的值是字符串，需要进行类型转换，以便计算
    for (let i in arr) {
        arr[i] = parseInt(arr[i]);
    }
    //返回处理后的数组
    return arr;
}

/**  把数组相邻且相等的值相加 Create By YangHuiJun
 * 
 * @param  arr 需要进行相加的数组 
 */
Board.prototype.addSame = function (arr) {
    //对数组进行移0处理
    arr = this.moveNumZero(arr);
    //遍历数组进行计算
    //当arr[index] = 0时，由于上面进行过移0操作，所以排在其后面的值都为0，则不用再继续遍历计算
    for (let index = 0; index < 4 && arr[index] != 0; index ++) {
        //如果相邻两个数相等且不为0，则把他们相加，并把和放在后面的位置，前面的位置重新赋值为0，再把下标往后移一位
        if (arr[index] == arr[index + 1] && arr[index] != 0) {
            arr[index + 1] *= 2;
            arr[index] = 0;
            index += 1;
        }
    }
    //对计算结果进行移0
    arr = this.moveNumZero(arr);

    //返回处理后的数组
    return arr;
}

/**
 * 检查棋子是否还可以继续移动，true表示可以继续移动，false表示不能再移动，即游戏结束
 */
Board.prototype.canMove = function(){
    //遍历数组
    for(let x = 0; x < 4; x ++){
        for(let y = 0; y < 4; y ++){
            //如果棋盘中有空位置，则棋子可以再次移动
            if(this.data[x][y] === 0){
                return true;
            }
            //相邻的棋子相等时，则棋子可以再次移动
            if(this.data[x][y] === this.data[x][y + 1] || 
                this.data[x][y] === (this.data[x + 1] ? this.data[x + 1][y] : null)){
                    return true;
            }
        }
    }
    return false;
}

/**
 * Create By YangHUiJun
 */

/** 棋子向左移动
 * 
 * @param  view  为View对象，用于调用该对象的addMove和move方法移动棋子
 */
Board.prototype.moveLeft = function (view) {
    //noMove用于记录不可操作的行数
    let noMove = 0;
    for (let x = 0; x < 4; x++) {
        //取出当前行的数据
        let arr = this.data[x];
        //判断当前行的数据是否全为0，则当前行的没有数据需移动，则当前行为不可操作行，noMove进行记录，并跳过本轮循环
        if (!arr.some(item => item !== 0)) {
            noMove += 1;
            continue;
        }
        //对当前行的数据进行移0和相等的数据进行相加处理 
        let zeroAfter = this.addSame(arr);
        //如果处理后的数据和原数据相同，则该行则当前行的没有数据需移动，则当前行为不可操作行，noMove进行记录，并跳过本轮循环
        if (arr.toString() == zeroAfter.toString()) {
            noMove += 1;
            continue;
        }
        //把处理后的数据放回原二维数组
        this.data[x] = zeroAfter;
        //遍历处理前和处理后的数据，找到处理前非零的数据和处理后的对应的数据，判断位置变化，并执行移动
        for (let before = 0, after = 0; before < 4; before++) {

            //判断当前数据是否为0，为0则跳过本轮循环
            if (arr[before] == 0) {
                continue;
            }
            //如果处理后的数据比处理前的对应数据大，则表示该数据是处理前的两个相邻的非0数据相加的结果，即棋子合并效果
            //否则仅进行移动效果
            if (arr[before] < zeroAfter[after]) {

                //nextNum用于记录与当前数据相加的相邻的第一个非0数据的下标
                let nextNum;
                //遍历处理前剩下的数据，找到相邻的第一个非0数据
                for (let next = before + 1; next < 4; next++) {
                    if (arr[next] != 0) {
                        //记录该数据的下标，并结束循环
                        nextNum = next;
                        break;
                    }
                }
                //对棋子进行移动合并
                view.addMove({ x: x, y: before }, { x: x, y: nextNum }, { x: x, y: after, num: zeroAfter[after] });
                //更新得分
                this.score += zeroAfter[after];
                view.setScore(this.score);

                //游戏胜利
                if(zeroAfter[after] === 2048){
                    $(view.game_result).text("您胜利了！").css({'background':'#FF4500','color':"#fff"}).animate({height: 60},600);
                    this.WIN_OR_FAIL = 1;
                }
                //把遍历处理前数据的下标移到与当前数据相加的那个数据位置，遍历处理后的数据的下标往后移一位
                before = nextNum;
                after += 1;

            } else {
                //判断处理前的数据和处理后的对应数据的位置是否一样，一样则不需移动，跳过本次循环，把遍历处理后的数据的下标往后移一位
                if (x + '-' + after == x + '-' + before) {
                    after += 1;
                    continue;
                }
                //对棋子进行移动
                view.move({ x: x, y: before },{ x: x, y: after });
                //把遍历处理后的数据的下标往后移一位
                after += 1;
            }
        }
    }
    //如果每行都不能移动，则返回
    if (noMove == 4) {
        return
    }
    //设置定时器，等待上面的移动完成后，增加一个新的棋子
    addView = attribute => setTimeout(function () { attribute }, 1000);
    addView(this.findZero(view));
    //游戏失败
    if(!this.canMove()){
        $(view.game_result).text("您失败了！").css({'background':'#D3D3D3','color':'#000'}).animate({height: 60},600);
        //记录游戏失败状态
        this.WIN_OR_FAIL = 2;
        return
    }
}

/** 棋子向右移动
 * 
 * @param  view  为View对象，用于调用该对象的addMove和move方法移动棋子
 */
Board.prototype.moveRight = function (view) {
    //noMove用于记录不可操作的行数
    let noMove = 0;
    for (let x = 0; x < 4; x++) {
        //反向取出当前行的数据，便于移0
        let arr = [];
        for (let y = 3; y >= 0; y--) {
            arr[-(y - 3)] = this.data[x][y];
        }
        //判断当前行的数据是否全为0，则当前行的没有数据需移动，则当前行为不可操作行，noMove进行记录，并跳过本轮循环
        if (!arr.some(item => item !== 0)) {
            noMove += 1;
            continue;
        }
        //对当前行的数据进行移0和相等的数据进行相加处理 
        let zeroAfter = this.addSame(arr);
        //如果处理后的数据和原数据相同，则该行没有数据需移动，则当前行为不可操作行，noMove进行记录，并跳过本轮循环
        if (arr.toString() == zeroAfter.toString()) {
            noMove += 1;
            continue;
        }
        //把处理后的数据放回原二维数组
        for (let y = 0; y < 4; y++) {
            this.data[x][y] = zeroAfter[-(y - 3)];
        }
        //遍历处理前和处理后的数据，找到处理前非零的数据和处理后的对应的数据，判断位置变化，并执行移动
        for (let before = 0, after = 0; before < 4; before++) {

            //判断当前数据是否为0，为0则跳过本轮循环
            if (arr[before] == 0) {
                continue;
            }
            //由于是反向取数据，需要转换对应的下标
            let afterNum = -(after - 3);
            let beforeNum = -(before - 3);
            //如果处理后的数据比处理前的对应数据大，则表示该数据是处理前的两个相邻的非0数据相加的结果，即棋子合并效果
            //否则仅进行移动效果
            if (arr[before] < zeroAfter[after]) {
                //nextNum用于记录与当前数据相加的相邻的第一个非0数据的下标
                let nextNum;
                //遍历处理前剩下的数据，找到相邻的第一个非0数据
                for (let next = before + 1; next < 4; next++) {
                    if (arr[next] != 0) {
                        //记录该数据的下标，并结束循环
                        nextNum = -(next - 3);
                        break;
                    }
                }
                //对棋子进行移动合并
                view.addMove({ x: x, y: beforeNum }, { x: x, y: nextNum }, { x: x, y: afterNum, num: zeroAfter[after] });
                //更新得分
                this.score += zeroAfter[after];
                view.setScore(this.score);

                //游戏胜利
                if(zeroAfter[after] === 2048){
                    $(view.game_result).text("您胜利了！").css({'background':'#FF4500','color':"#fff"}).animate({height: 60},600);
                    this.WIN_OR_FAIL = 1;
                }
                //把遍历处理前数据的下标移到与当前数据相加的那个数据位置，遍历处理后的数据的下标往后移一位
                before = -(nextNum - 3);
                after += 1;
            } else {
                //判断处理前的数据和处理后的对应数据的位置是否一样，一样则不需移动，跳过本次循环，把遍历处理后的数据的下标往后移一位
                if (x + '-' + afterNum == x + '-' + beforeNum) {
                    after += 1;
                    continue;
                }
                //对棋子进行移动
                view.move({ x: x, y: beforeNum },{ x: x, y: afterNum });
                //把遍历处理后的数据的下标往后移一位
                after += 1;
            }
        }
    }
    
    //如果每行都不能移动，则返回
    if (noMove == 4) {
        return
    }
    //设置定时器，等待上面的移动完成后，增加一个新的棋子
    addView = attribute => setTimeout(function () { attribute }, 1000);
    addView(this.findZero(view));
    //游戏失败
    if(!this.canMove()){
        $(view.game_result).text("您失败了！").css({'background':'#D3D3D3','color':'#000'}).animate({height: 60},600);
        //记录游戏失败状态
        this.WIN_OR_FAIL = 2;
        return
    }
}

/** 棋子向上移动
 * 
 * @param  view  为View对象，用于调用该对象的addMove和move方法移动棋子
 */
Board.prototype.moveUp = function (view) {
    //noMove用于记录不可操作的列数
    let noMove = 0;
    for (let y = 0; y < 4; y++) {
        //取出当前列的数据
        let arr = [];
        for (let x = 0; x < 4; x++) {
            arr[x] = this.data[x][y];
        }
        //判断当前列的数据是否全为0，则当前列的没有数据需移动，则当前列为不可操作列，noMove进行记录，并跳过本轮循环
        if (!arr.some(item => item !== 0)) {
            noMove += 1;
            continue;
        }
        //对当前列的数据进行移0和相等的数据进行相加处理 
        let zeroAfter = this.addSame(arr);
        //如果处理后的数据和原数据相同，则该列没有数据需移动，则当前列为不可操作列，noMove进行记录，并跳过本轮循环
        if (arr.toString() == zeroAfter.toString()) {
            noMove += 1;
            continue;
        }
        //把处理后的数据放回原二维数组
        for (let x = 0; x < 4; x++) {
            this.data[x][y] = zeroAfter[x];
        }
        //遍历处理前和处理后的数据，找到处理前非零的数据和处理后的对应的数据，判断位置变化，并执行移动
        for (let before = 0, after = 0; before < 4; before++) {

            //判断当前数据是否为0，为0则跳过本轮循环
            if (arr[before] == 0) {
                continue;
            }
            //如果处理后的数据比处理前的对应数据大，则表示该数据是处理前的两个相邻的非0数据相加的结果，即棋子合并效果
            //否则仅进行移动效果
            if (arr[before] < zeroAfter[after]) {

                //nextNum用于记录与当前数据相加的相邻的第一个非0数据的下标
                let nextNum;
                //遍历处理前剩下的数据，找到相邻的第一个非0数据
                for (let next = before + 1; next < 4; next++) {
                    if (arr[next] != 0) {
                        //记录该数据的下标，并结束循环
                        nextNum = next;
                        break;
                    }
                }
                //对棋子进行移动合并
                view.addMove({ x: before, y: y }, { x: nextNum, y: y }, { x: after, y: y, num: zeroAfter[after] });
                //更新得分
                this.score += zeroAfter[after];
                view.setScore(this.score);

                //游戏胜利
                if(zeroAfter[after] === 2048){
                    $(view.game_result).text("您胜利了！").css({'background':'#FF4500','color':"#fff"}).animate({height: 60},600);
                    this.WIN_OR_FAIL = 1;
                }
                //把遍历处理前数据的下标移到与当前数据相加的那个数据位置，遍历处理后的数据的下标往后移一位
                before = nextNum;
                after += 1;

            } else {
                //判断处理前的数据和处理后的对应数据的位置是否一样，一样则不需移动，跳过本次循环，把遍历处理后的数据的下标往后移一位
                if (after + '-' + y == before + '-' + y) {
                    after += 1;
                    continue;
                }
                //对棋子进行移动
                view.move({ x: before, y: y },{ x: after, y: y });
                //把遍历处理后的数据的下标往后移一位
                after += 1;
            }
        }
    }
    //如果每列都不能移动，则返回
    if (noMove == 4) {
        return
    }
    //设置定时器，等待上面的移动完成后，增加一个新的棋子
    addView = attribute => setTimeout(function () { attribute }, 1000);
    addView(this.findZero(view));
    //游戏失败
    if(!this.canMove()){
        $(view.game_result).text("您失败了！").css({'background':'#D3D3D3','color':'#000'}).animate({height: 60},600);
        //记录游戏失败状态
        this.WIN_OR_FAIL = 2;
        return
    }
}

/** 棋子向下移动
 * 
 * @param  view  为View对象，用于调用该对象的addMove和move方法移动棋子
 */
Board.prototype.moveDown = function (view) {
    //noMove用于记录不可操作的列数
    let noMove = 0;
    for (let y = 0; y < 4; y++) {
        //反向取出当前列的数据，便于移0
        let arr = [];
        for (let x = 3; x >= 0; x--) {
            arr[-(x - 3)] = this.data[x][y];
        }
        //判断当前列的数据是否全为0，则当前列的没有数据需移动，则当前列为不可操作列，noMove进行记录，并跳过本轮循环
        if (!arr.some(item => item !== 0)) {
            noMove += 1;
            continue;
        }
        //对当前列的数据进行移0和相等的数据进行相加处理 
        let zeroAfter = this.addSame(arr);
        //如果处理后的数据和原数据相同，则该列没有数据需移动，则当前列为不可操作列，noMove进行记录，并跳过本轮循环
        if (arr.toString() == zeroAfter.toString()) {
            noMove += 1;
            continue;
        }
        //把处理后的数据放回原二维数组
        for (let x = 0; x < 4; x++) {
            this.data[x][y] = zeroAfter[-(x - 3)];
        }
        //遍历处理前和处理后的数据，找到处理前非零的数据和处理后的对应的数据，判断位置变化，并执行移动
        for (let before = 0, after = 0; before < 4; before++) {

            //判断当前数据是否为0，为0则跳过本轮循环
            if (arr[before] == 0) {
                continue;
            }
            //由于是反向取数据，需要转换对应的下标
            let afterNum = -(after - 3);
            let beforeNum = -(before - 3);
            //如果处理后的数据比处理前的对应数据大，则表示该数据是处理前的两个相邻的非0数据相加的结果，即棋子合并效果
            //否则仅进行移动效果
            if (arr[before] < zeroAfter[after]) {
                //nextNum用于记录与当前数据相加的相邻的第一个非0数据的下标
                let nextNum;
                //遍历处理前剩下的数据，找到相邻的第一个非0数据
                for (let next = before + 1; next < 4; next++) {
                    if (arr[next] != 0) {
                        //记录该数据的下标，并结束循环
                        nextNum = -(next - 3);
                        break;
                    }
                }
                //对棋子进行移动合并
                view.addMove({ x: beforeNum, y: y }, { x: nextNum, y: y }, { x: afterNum, y: y, num: zeroAfter[after] });
                //更新得分
                this.score += zeroAfter[after];
                view.setScore(this.score);

                //游戏胜利
                if(zeroAfter[after] === 2048){
                    $(view.game_result).text("您胜利了！").css({'background':'#FF4500','color':"#fff"}).animate({height: 60},600);
                    this.WIN_OR_FAIL = 1;
                }
                //把遍历处理前数据的下标移到与当前数据相加的那个数据位置，遍历处理后的数据的下标往后移一位
                before = -(nextNum - 3);
                after += 1;
            } else {
                //判断处理前的数据和处理后的对应数据的位置是否一样，一样则不需移动，跳过本次循环，把遍历处理后的数据的下标往后移一位
                if (afterNum + '-' + y == beforeNum + '-' + y) {
                    after += 1;
                    continue;
                }
                //对棋子进行移动
                view.move({ x: beforeNum, y: y },{ x: afterNum, y: y });
                //把遍历处理后的数据的下标往后移一位
                after += 1;
            }
        }
    }
    //如果每列都不能移动，则返回
    if (noMove == 4) {
        return
    }
    //设置定时器，等待上面的移动完成后，增加一个新的棋子
    addView = attribute => setTimeout(function () { attribute }, 1000);
    addView(this.findZero(view));
    //游戏失败
    if(!this.canMove()){
        $(view.game_result).text("您失败了！").css({'background':'#D3D3D3','color':'#000'}).animate({height: 60},600);
        //记录游戏失败状态
        this.WIN_OR_FAIL = 2;
        return
    }
}

/**
 *  Create By YangHUiJun
 */